use crate::parser::*;
use crate::tools;
use crate::tools::*;

#[derive(Debug, PartialEq, Eq, Clone)]
struct Element {
    name: String,
    attrs: Vec<(String, String)>,
    children: Vec<Element>,
}

// zero_or_more 搭配 any_char 造成了不必要的拷贝
pub fn quoted_string<'a>(input: &'a str) -> ParserResult<'a, String> {
    map(
        right(
            match_literal("\""),
            left(
                zero_or_more(pred(any_char, |c| *c != '"')),
                match_literal("\""),
            ),
        ),
        |chars| chars.into_iter().collect(),
    )
    .parse(input)
}

fn attr_pair<'a>(input: &'a str) -> ParserResult<'a, (&str, String)> {
    pair(identifier, right(match_literal("="), quoted_string)).parse(input)
}

fn attributes<'a>(input: &'a str) -> ParserResult<'a, Vec<(&'a str, String)>> {
    zero_or_more(right(space, attr_pair)).parse(input)
}

fn element_start<'a>(input: &'a str) -> ParserResult<'a, (&str, Vec<(&'a str, String)>)> {
    right(match_literal("<"), pair(identifier, attributes)).parse(input)
}

fn single_element<'a>(input: &'a str) -> ParserResult<'a, Element> {
    map(
        left(element_start, match_literal("/>")),
        |(name, attrs_str)| Element {
            name: name.into(),
            children: vec![],
            attrs: attrs_str
                .into_iter()
                .map(|(name_str, value)| (name_str.into(), value))
                .collect(),
        },
    )
    .parse(input)
}

fn open_element<'a>(input: &'a str) -> ParserResult<'a, Element> {
    element_start
        .ignore(match_literal(">"))
        .map(|(name, attrs_str)| Element {
            name: name.into(),
            children: vec![],
            attrs: attrs_str
                .into_iter()
                .map(|(name_str, value)| (name_str.into(), value))
                .collect(),
        })
        .parse(input)
}

fn create_close_element<'a>(except_name: String) -> impl Parser<'a, &'a str> {
    right(match_literal("</"), left(identifier, match_literal(">")))
        .pred(move |name| **name == except_name)
}

fn parent_element<'a>(input: &'a str) -> ParserResult<'a, Element> {
    pair(
        element_start.ignore(match_literal(">")),
        zero_or_more(element),
    )
    .map(|((name, attrs_str), children)| Element {
        name: name.to_string(),
        children,
        attrs: attrs_str
            .into_iter()
            .map(|(name_str, value)| (name_str.into(), value))
            .collect(),
    })
    .parse(input)
    .map_err(|_| "open error")
    .and_then(|(next, el)| {
        create_close_element(el.name.clone())
            .parse(next)
            .map(|(output, _)| (output, el))
            .map_err(|_| "close error")
    })
}

fn element<'a>(input: &'a str) -> ParserResult<'a, Element> {
    whitespace_wrap(either(single_element, parent_element)).parse(input)
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn xml_parser_easy() {
        let doc = r#"<top a="b"> <fuck c="d"/> </top> "#;

        let result = pair(
            element_start.ignore(match_literal(">")),
            zero_or_more(element),
        )
        .parse(doc);

        println!("{:?}", result);
    }

    #[test]
    fn xml_parser() {
        let doc = r#"<top label="Top"><semi-bottom label="Bottom"/><middle><bottom label="Another bottom"/></middle></top>"#;
        let result = element(doc);

        let parsed_doc = Element {
            name: "top".to_string(),
            attrs: vec![("label".to_string(), "Top".to_string())],
            children: vec![
                Element {
                    name: "semi-bottom".to_string(),
                    attrs: vec![("label".to_string(), "Bottom".to_string())],
                    children: vec![],
                },
                Element {
                    name: "middle".to_string(),
                    attrs: vec![],
                    children: vec![Element {
                        name: "bottom".to_string(),
                        attrs: vec![("label".to_string(), "Another bottom".to_string())],
                        children: vec![],
                    }],
                },
            ],
        };
        assert_eq!(Ok(("", parsed_doc)), result);
    }

    #[test]
    fn single_element_parser() {
        let result = single_element("<div class=\"float\"/>");
        assert_eq!(
            Ok((
                "",
                Element {
                    name: "div".to_string(),
                    attrs: vec![("class".to_string(), "float".to_string())],
                    children: vec![]
                }
            )),
            result
        );
    }

    #[test]
    fn quoted_string_parser() {
        assert_eq!(
            Ok((" fuck", "Hello Joe!".to_string())),
            quoted_string("\"Hello Joe!\" fuck")
        );
    }

    #[test]
    fn attribute_parser() {
        assert_eq!(
            Ok(("", vec![("one", "1".to_string()), ("two", "2".to_string())])),
            attributes(" one=\"1\" two=\"2\"")
        );
    }

    #[test]
    fn attr_pair_parser() {
        assert_eq!(
            Ok((" fuck", ("hello", "girl".to_string()))),
            attr_pair("hello=\"girl\" fuck")
        )
    }
}
